package com.md.util;

import com.md.base.exception.ServerException;
import com.md.value.ImageVerification;
import com.md.value.enums.ErrorEnum;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Random;

/**
 * @author 刘骄阳
 * 2023-08-22 14:26
 */
@Slf4j
@UtilityClass
public class ImageVerificationUtil {

  //  默认图片宽度
  private static final int DEFAULT_IMAGE_WIDTH = 280;

  //  默认图片高度
  private static final int DEFAULT_IMAGE_HEIGHT = 171;

  /**
   * 16进制表示的纯白
   */
  private static final int WIGHT = 0xFFFFFF;

  /**
   * 16进制表示的蓝色
   * -7237488
   * 对于给定的数值-7237488，转换为非负的RGB值进行解释：
   * positiveRGB = 16777216 - 7237488 = -7237488 + (1 << 32)
   * 计算后我们得到其对应的RGB值，然后可以将其拆分为R、G、B各分量
   * int red   = (positiveRGB >> 16) & 0xFF;
   * int green = (positiveRGB >> 8)  & 0xFF;
   * int blue  = positiveRGB         & 0xFF;
   */
  private static final int BLUE = 0X0000FF;

  /**
   * 255
   */
  private static final int MAX = 0xFF;

  /**
   * 生成感兴趣区域坐标
   *
   * @param verificationImage 源图
   * @param templateImage     模板图
   * @return 裁剪坐标
   */
  public static ImageVerification generateCutoutCoordinates(final BufferedImage verificationImage,
                                                            final BufferedImage templateImage) {

    int X, Y;
    ImageVerification imageVerificationVo = null;


//        int VERIFICATION_IMAGE_WIDTH = verificationImage.getWidth();  //  原图宽度
//        int VERIFICATION_IMAGE_HEIGHT = verificationImage.getHeight();  //  原图高度
    int TEMPLATE_IMAGE_WIDTH = templateImage.getWidth();   //  抠图模板宽度
    int TEMPLATE_IMAGE_HEIGHT = templateImage.getHeight();  //  抠图模板高度

    Random random = new Random(System.currentTimeMillis());

    //  取范围内坐标数据，坐标抠图一定要落在原图中，否则会导致程序错误
    X = random.nextInt(DEFAULT_IMAGE_WIDTH - TEMPLATE_IMAGE_WIDTH) % (DEFAULT_IMAGE_WIDTH - TEMPLATE_IMAGE_WIDTH - TEMPLATE_IMAGE_WIDTH + 1) + TEMPLATE_IMAGE_WIDTH;
    Y = random.nextInt(DEFAULT_IMAGE_HEIGHT - TEMPLATE_IMAGE_WIDTH) % (DEFAULT_IMAGE_HEIGHT - TEMPLATE_IMAGE_WIDTH - TEMPLATE_IMAGE_WIDTH + 1) + TEMPLATE_IMAGE_WIDTH;
    if (TEMPLATE_IMAGE_HEIGHT - DEFAULT_IMAGE_HEIGHT >= 0) {
      Y = random.nextInt(10);
    }
    imageVerificationVo = new ImageVerification();
    imageVerificationVo.setX(X);
    imageVerificationVo.setY(Y);

    return imageVerificationVo;
  }

  /**
   * 根据模板图裁剪图片，生成源图遮罩图和裁剪图
   *
   * @param originImageFile       源图文件
   * @param originImageFileType   源图文件扩展名
   * @param templateImageFile     模板图文件
   * @param templateImageFileType 模板图文件扩展名
   * @param x                     感兴趣区域X轴
   * @param y                     感兴趣区域Y轴
   * @return ImageVerification
   */
  public static ImageVerification pictureTemplateCutout(final File originImageFile,
                                                        final String originImageFileType,
                                                        final File templateImageFile,
                                                        final String templateImageFileType,
                                                        final int x, final int y) {
    ImageVerification imageVerificationVo = new ImageVerification();


    try {
      //  读取模板图
      BufferedImage templateImage = ImageIO.read(templateImageFile);
      //  读取原图
      BufferedImage originImage = ImageIO.read(originImageFile);
      int originalImageWidth = templateImage.getWidth();
      int originalImageHeight = templateImage.getHeight();

      //  切块图   根据模板图尺寸创建一张透明图片
      BufferedImage cutoutImage = new BufferedImage(originalImageWidth, originalImageHeight, templateImage.getType());

      //  根据坐标获取感兴趣区域
      BufferedImage interestArea = getInterestArea(x, y, originalImageWidth, originalImageHeight, originImageFile, originImageFileType);

      //  根据模板图片切图
      cutoutImageByTemplateImage(interestArea, templateImage, cutoutImage);

      //  图片绘图
      int bold = 5;
      Graphics2D graphics2D = cutoutImage.createGraphics();
      graphics2D.setBackground(Color.white);

      //  设置抗锯齿属性
      graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      graphics2D.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
      graphics2D.drawImage(cutoutImage, 0, 0, null);
      graphics2D.dispose();

      //  原图生成遮罩
      BufferedImage shadeImage = generateShadeByTemplateImage(originImage, templateImage, x, y);

      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      //  原始图片转为二进制字符串
      ImageIO.write(originImage, originImageFileType, byteArrayOutputStream);
//      FileUtil.writeToFile(byteArrayOutputStream, "D://file.png");
      byte[] originImageBytes = byteArrayOutputStream.toByteArray();
      byteArrayOutputStream.flush();
      byteArrayOutputStream.reset();
      //  图片加密成base64字符串
      imageVerificationVo.setOriginImage(Base64Utils.encodeToString(originImageBytes));

      // 带遮罩图片转为二进制字符串
      ImageIO.write(shadeImage, templateImageFileType, byteArrayOutputStream);
//      FileUtil.writeToFile(byteArrayOutputStream, "D://file1.png");
      byte[] shadeImageBytes = byteArrayOutputStream.toByteArray();
      byteArrayOutputStream.flush();
      byteArrayOutputStream.reset();
      //  图片加密成base64字符串
      imageVerificationVo.setShadeImage(Base64Utils.encodeToString(shadeImageBytes));

      // 遮罩图片转为二进制字符串
      ImageIO.write(cutoutImage, templateImageFileType, byteArrayOutputStream);
//      FileUtil.writeToFile(byteArrayOutputStream, "D://file2.png");
      byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
      byteArrayOutputStream.reset();
      //  图片加密成base64字符串
      imageVerificationVo.setCutoutImage(Base64Utils.encodeToString(cutoutImageBytes));
    } catch (IOException e) {
      log.error("读取图片发生异常: {}", e.getMessage(), e);
      throw ServerException.build(ErrorEnum.READ_FILE_ERROR);
    }
    return imageVerificationVo;
  }

  /**
   * 根据模板图生成遮罩图
   *
   * @param originImage   源图
   * @param templateImage 模板图
   * @param x             感兴趣区域X轴
   * @param y             感兴趣区域Y轴
   * @return 遮罩图
   * @throws IOException 数据转换异常
   */
  private static BufferedImage generateShadeByTemplateImage(final BufferedImage originImage,
                                                            final BufferedImage templateImage,
                                                            final int x, final int y) {
    //  根据原图，创建支持alpha通道的rgb图片
    BufferedImage shadeImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(), BufferedImage.TYPE_INT_ARGB);

    //  原图片矩阵
    int[][] originImageMatrix = getMatrix(originImage);
    //  模板图片矩阵
    int[][] templateImageMatrix = getMatrix(templateImage);

    //  将原图的像素拷贝到遮罩图
    for (int i = 0; i < originImageMatrix.length; i++) {
      for (int j = 0; j < originImageMatrix[0].length; j++) {
        int rgb = originImage.getRGB(i, j);
        //  获取rgb色度
        int r = (MAX & rgb);
        int g = (MAX & (rgb >> 8));
        int b = (MAX & (rgb >> 16));
        //  无透明处理
        rgb = r + (g << 8) + (b << 16) + (MAX << 24);
        shadeImage.setRGB(i, j, rgb);
      }
    }

    //  对遮罩图根据模板像素进行处理
    for (int i = 0; i < templateImageMatrix.length; i++) {
      for (int j = 0; j < templateImageMatrix[0].length; j++) {
        int rgb = templateImage.getRGB(i, j);

        //对源文件备份图像(x+i,y+j)坐标点进行透明处理
        if (rgb != WIGHT && rgb < 0) {
          int rgbOri = shadeImage.getRGB(x + i, y + j);
          int r = (MAX & rgbOri);
          int g = (MAX & (rgbOri >> 8));
          int b = (MAX & (rgbOri >> 16));


          rgbOri = r + (g << 8) + (b << 16) + (140 << 24);

          //  对遮罩透明处理,设置遮罩颜色
          shadeImage.setRGB(x + i, y + j, rgbOri);
        }

      }
    }

    return shadeImage;
  }

  /**
   * 根据模板图抠图
   *
   * @param interestArea  感兴趣区域图
   * @param templateImage 模板图
   * @param cutoutImage   裁剪图
   * @return 裁剪图
   */
  private static void cutoutImageByTemplateImage(final BufferedImage interestArea,
                                                 final BufferedImage templateImage,
                                                 final BufferedImage cutoutImage) {
    //  获取模板图片矩阵
    int[][] templateImageMatrix = getMatrix(templateImage);

    //  将模板图非透明像素设置到剪切图中
    for (int i = 0; i < templateImageMatrix.length; i++) {
      for (int j = 0; j < templateImageMatrix[0].length; j++) {
        int rgb = templateImageMatrix[i][j];
        if (rgb != WIGHT && rgb < 0) {
          cutoutImage.setRGB(i, j, interestArea.getRGB(i, j));
        }
      }
    }
  }

  /**
   * 图片生成图像矩阵
   *
   * @param bufferedImage 图片源
   * @return 图片矩阵
   */
  private static int[][] getMatrix(final BufferedImage bufferedImage) {
    int[][] matrix = new int[bufferedImage.getWidth()][bufferedImage.getHeight()];
    for (int i = 0; i < bufferedImage.getWidth(); i++) {
      for (int j = 0; j < bufferedImage.getHeight(); j++) {
        matrix[i][j] = bufferedImage.getRGB(i, j);
      }
    }
    return matrix;
  }

  /**
   * 获取感兴趣区域
   *
   * @param x                   感兴趣区域X轴
   * @param y                   感兴趣区域Y轴
   * @param originalImageWidth  模板图宽度
   * @param originalImageHeight 模板图高度
   * @param originImage         源图
   * @param originImageType     源图扩展名
   * @return BufferedImage
   */
  private static BufferedImage getInterestArea(final int x, final int y, final int originalImageWidth,
                                               final int originalImageHeight, final File originImage,
                                               final String originImageType) {

    try {
      Iterator<ImageReader> imageReaderIterator = ImageIO.getImageReadersByFormatName(originImageType);
      ImageReader imageReader = imageReaderIterator.next();
      //  获取图片流
      ImageInputStream imageInputStream = ImageIO.createImageInputStream(originImage);
      //  图片输入流顺序读写
      imageReader.setInput(imageInputStream, true);
      ImageReadParam imageReadParam = imageReader.getDefaultReadParam();
      //  根据坐标生成矩形
      Rectangle rectangle = new Rectangle(x, y, originalImageWidth, originalImageHeight);
      imageReadParam.setSourceRegion(rectangle);
      return imageReader.read(0, imageReadParam);
    } catch (IOException e) {
      log.error("读取文件失败: {}", e.getMessage(), e);
      throw ServerException.build(ErrorEnum.READ_FILE_ERROR);
    }
  }

  /**
   * 切块图描边
   *
   * @param imageVerificationVo 图片容器
   * @param borderImage         描边图
   * @param borderImageFileType 描边图类型
   * @return 图片容器
   */
  public static ImageVerification cutoutImageEdge(final ImageVerification imageVerificationVo,
                                                  final BufferedImage borderImage,
                                                  final String borderImageFileType) {
    try {
      String cutoutImageString = imageVerificationVo.getCutoutImage();
      //  图片解密成二进制字符创
      byte[] bytes = Base64Utils.decodeFromString(cutoutImageString);
      ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
      //  读取图片
      BufferedImage cutoutImage = ImageIO.read(byteArrayInputStream);
      //  获取模板边框矩阵， 并进行颜色处理
      int[][] borderImageMatrix = getMatrix(borderImage);
      for (int i = 0; i < borderImageMatrix.length; i++) {
        for (int j = 0; j < borderImageMatrix[0].length; j++) {
          int rgb = borderImage.getRGB(i, j);
          if (rgb < 0) {
            cutoutImage.setRGB(i, j, BLUE);
          }
        }
      }
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      ImageIO.write(cutoutImage, borderImageFileType, byteArrayOutputStream);
      //  新模板图描边处理后转成二进制字符串
      byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
      //  二进制字符串加密成base64字符串
      String cutoutImageStr = Base64Utils.encodeToString(cutoutImageBytes);
      imageVerificationVo.setCutoutImage(cutoutImageStr);
    } catch (IOException e) {
      log.error(e.getMessage(), e);
      throw ServerException.build(ErrorEnum.READ_FILE_ERROR);
    }
    return imageVerificationVo;
  }
}
